/**
 *  EKology - An Ecosystem Simulation Framework
 *  Copyright (C) 2008  Aarón Tavío - aaron.tavio at gmail.com - ULPGC
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package net.ekology.ekosystem;

import net.ekology.ekosystem.datatypes.*;
import net.ekology.core.datatypes.*;
import net.ekology.graphics.*;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.lang.reflect.*;
import java.io.Serializable;


/**
 * The <code>Agent</code> class is extended by <code>AbioticAgent</code> and
 * <code>BioticAgent</code> providing numerous methods, many of which should be
 * used to obtain new functionalities
 * 
 * @author Aarón Tavío - aaron.tavio at gmail.com
 * @version 1.0.0 - 20081019-1700
 */
public abstract class Agent implements Drawable, Serializable {
    private String sID;
    private Calendar oCreationDate;
    private EKVector oPosition;
    private EKVector oOrientation;
    protected Biotope oBiotope;
    
    

    public Agent() {
        this.sID = null;
        this.oBiotope = null;
        this.oCreationDate = null;
    }
    
    public Agent(String sID, Biotope oBiotope) {
        this.sID = sID;
        this.oBiotope = oBiotope;
        this.oCreationDate = (Calendar) oBiotope.getLocalDate().clone();
    }
    
    public Agent(String sID, Biotope oBiotope, Calendar oCreationDate) {
        this.sID = sID;
        this.oBiotope = oBiotope;
        this.oCreationDate = oCreationDate;
    }
    
    public String getID() {
        return this.sID;
    }
    
    public void setID(String sID) {
        if (this.sID == null) this.sID = sID;
    }
    
    public void setBiotope(Biotope oBiotope) {
        if (this.oBiotope == null) {
            this.oBiotope = oBiotope;
            this.oCreationDate = (Calendar) oBiotope.getLocalDate().clone();
        }
    }
    
    public Calendar getCreationDate() {
        return this.oCreationDate;
    }
    
    
    /**
     * Returns the age of the current <code>Agent</code>
     * 
     * @param iUnits can be Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.DAY_OF_YEAR, Calendar.HOUR, Calendar.MINUTE, Calendar.SECOND or Calendar.MILLISECOND
     * @return the amount of time units
     */
    public long getAge(int iUnits) {
        long lResult = 0;
        long lFactor = 1;
        Calendar oCalendar = new GregorianCalendar();
        
        oCalendar.setTimeInMillis(oBiotope.getLocalDate().getTimeInMillis()-getCreationDate().getTimeInMillis());
        
        switch(iUnits) {
            case Calendar.YEAR:
                lFactor *= 12;
            case Calendar.MONTH:
                lFactor *= 30;
            case Calendar.DAY_OF_MONTH:
            case Calendar.DAY_OF_WEEK:
            case Calendar.DAY_OF_WEEK_IN_MONTH:
            case Calendar.DAY_OF_YEAR:
                lFactor *= 24;
            case Calendar.HOUR:
                lFactor *= 60;
            case Calendar.MINUTE:
                lFactor *= 60;
            case Calendar.SECOND:
                lFactor *= 1000;
            case Calendar.MILLISECOND: 
            default:
                lResult = oCalendar.getTimeInMillis()/lFactor;
        }
               
        return lResult;
    }
    
    public Automaton getAutomaton() {
        return (this instanceof BioticAgent)? BioticAgent.class.cast(this).getGenome().getAutomaton() : AbioticAgent.class.cast(this).getAutomaton();
    }
    
    public String getSpeciesName() {
        return this.getClass().getSimpleName();
    }
    
    public EKVector getPosition() {
        return oPosition.clone();
    }
    
    public void setPosition(EKVector oPosition) {
        this.oPosition = oPosition.clone();
    }
    
    public EKVector getOrientation() {
        return oOrientation.clone();
    }
    
    public void setOrientation(EKVector oOrientation) {
        this.oOrientation = oOrientation.clone().normalize();
    }
    
    
    /**
     * Gets the attribute names provided by the developer
     * <p>
     * Used in conjunction with <code>getAttributes()</code>, <code>getAttribute()</code>
     * and <code>setAttribute()</code>. Must be overriden.
     * 
     * @return an array of names
     */
    public String[] getAttributeNames() {
        // must be overriden
        return (String[]) null;
    }
    
    
    /**
     * Gets the attributes provided by the developer
     * <p>
     * Used in conjunction with <code>getAttributeNames()</code>, <code>getAttribute()</code>
     * and <code>setAttribute()</code>. Must be overriden.
     * 
     * @return an array of Attributes
     */
    public Attribute[] getAttributes() {
        String[] aName;
        Attribute[] aResult = null;
        
        aName = getAttributeNames();
        if (aName != null) {
            aResult = new Attribute[aName.length];
            for(int i=0; i<aName.length; i++) {
                aResult[i] = getAttribute(aName[i]);
            }
        }
        
        return aResult;
    }
    
    
    /**
     * Gets an <code>Attribute</code> defined by the developer
     * <p>
     * Used in conjunction with <code>getAttributeNames()</code>, <code>getAttributes()</code>
     * and <code>setAttribute()</code>. Must be overriden.
     * 
     * @param sName the desired attribute's name
     * @return the <code>Attribute</code>
     */
    public Attribute getAttribute(String sName) {
        // must be overriden
        return (Attribute) null;
    }
    
    
    /**
     * Sets an <code>Attribute</code> defined by the developer
     * <p>
     * Used in conjunction with <code>getAttributeNames()</code> and <code>getAttributes()</code>.
     * Must be overriden.
     * 
     * @param oAttribute 
     */
    public void setAttribute(Attribute oAttribute) {
        // must be overriden
    }
    
    
    /**
     * Sets an <code>Attribute</code> defined by the developer
     * <p>
     * Used in conjunction with <code>getAttributeNames()</code> and <code>getAttributes()</code>.
     * Must be overriden.
     * 
     * @param sName the desired attribute's name
     * @param sValue the string representation of the value
     */
    public void setAttribute(String sName, String sValue) {
        // must be overriden
    }
    
    public boolean isBiotic() {
        return this.getClass().getSuperclass().getSimpleName().equals("BioticAgent");
    }
    
    
    public boolean isAbiotic() {
        return this.getClass().getSuperclass().getSimpleName().equals("AbioticAgent");
    }
    
    
    /**
     * By default when an _ENTRY method is invoked, a method named <code>sCurrentStateName+"_ENTRY"</code>
     * is called.
     * 
     * @param sCurrentStateName the name of the current state
     * @param oFiringEvent the firing event
     * @param iEKTimeSlice the time slice measured in ecosystem's time i.e. <code>time*dTimeFactor</code>
     * @return the milliseconds used measured in ecosystem's time
     */
    public int _ENTRY(String sCurrentStateName, EKEvent oFiringEvent, int iEKTimeSlice) {
        String sMethodName;
        Method oMethod;
        int iEKTimeUsed;
        
        iEKTimeUsed = 0;
        sMethodName = sCurrentStateName+"_ENTRY";
        try {
            oMethod = this.getClass().getMethod(sMethodName,EKEvent.class, int.class);
            iEKTimeUsed = (Integer) oMethod.invoke(this,oFiringEvent,iEKTimeSlice);
        }
        catch (NoSuchMethodException ignore) {System.out.printf("Agent.java: NoSuchMethodException;\n");}
        catch (IllegalAccessException ignore) {System.out.printf("Agent.java: IllegalAccessException;\n");}
        catch (InvocationTargetException e) {e.printStackTrace();}
        
        return iEKTimeUsed;
    }
    
    /**
     * By default when an _DO method is invoked, a method named <code>sCurrentStateName+"_DO"</code>
     * is called.
     * 
     * @param sCurrentStateName the name of the current state
     * @param iEKTimeSlice the time slice measured in ecosystem's time i.e. <code>time*dTimeFactor</code>
     * @return the milliseconds used measured in ecosystem's time
     */
    public int _DO(String sCurrentStateName, int iEKTimeSlice) {
        String sMethodName;
        Method oMethod;
        int iEKTimeUsed;
        
        iEKTimeUsed = 0;
        sMethodName = sCurrentStateName+"_DO";
        try {
            oMethod = this.getClass().getMethod(sMethodName,int.class);
            iEKTimeUsed = (Integer) oMethod.invoke(this,iEKTimeSlice);
        }
        catch (NoSuchMethodException ignore) {
            iEKTimeUsed = iEKTimeSlice; // to avoid deadlock
        }
        catch (IllegalAccessException ignore) {System.out.printf("Agent.java: IllegalAccessException;\n");}
        catch (InvocationTargetException e) {e.printStackTrace();}
        
        return iEKTimeUsed;
    }
    
    
    /**
     * By default when an _EXIT method is invoked, a method named <code>sCurrentStateName+"_EXIT"</code>
     * is called.
     * 
     * @param sCurrentStateName the name of the current state
     * @param oFiringEvent the firing event
     * @param iEKTimeSlice the time slice measured in ecosystem's time i.e. <code>time*dTimeFactor</code>
     * @return the milliseconds used measured in ecosystem's time
     */
    public int _EXIT(String sCurrentStateName, EKEvent oFiringEvent, int iEKTimeSlice) {
        String sMethodName;
        Method oMethod;
        int iEKTimeUsed;
        
        iEKTimeUsed = 0;
        sMethodName = sCurrentStateName+"_EXIT";
        try {
            oMethod = this.getClass().getMethod(sMethodName,EKEvent.class,int.class);
            iEKTimeUsed = (Integer) oMethod.invoke(this,oFiringEvent,iEKTimeSlice);
        }
        catch (NoSuchMethodException ignore) {System.out.printf("Agent.java: NoSuchMethodException;\n");}
        catch (IllegalAccessException ignore) {System.out.printf("Agent.java: IllegalAccessException;\n");}
        catch (InvocationTargetException e) {e.printStackTrace();}
        
        return iEKTimeUsed;
    }
    
    
    /**
     * Invoked right after the agent is created using the GUI.
     * <p>
     * To use this functionality, this method must be overriden by the agent.
     */
    public void initialize() {
        // should be overriden
    }
    
  

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Agent other = (Agent) obj;
        if (this.sID == null || !this.sID.equals(other.sID)) {
            return false;
        }

        return true;
    }

    
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 19 * hash + (this.sID != null ? this.sID.hashCode() : 0);
        return hash;
    }
    
    
    @Override
    public String toString() {
        return String.format("%s(%s): %s", getClass().getSimpleName(), getClass().getSuperclass().getSimpleName(), sID);
    }

}
